package com.jy.datapipeline.export.splitkey.handler;

import com.jy.datapipeline.common.util.RandomNumsUtil;
import com.jy.datapipeline.export.common.constants.SplitKeyExecuteConstant;
import com.jy.datapipeline.export.common.enums.impl.QueryTypeEnums;
import com.jy.datapipeline.export.common.enums.impl.TableTypeEnums;
import com.jy.datapipeline.export.dao.ExpTaskDao;
import com.jy.datapipeline.export.dao.repo.ExpTaskExecuteDetailLogDaoRepo;
import com.jy.datapipeline.export.entity.*;
import com.jy.datapipeline.export.quartz.JobDetailSingleShardHandler;
import com.jy.datapipeline.export.service.ExpRuleDetailService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;

import javax.sql.DataSource;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class SplitKeyTaskHandler {

    private final int printOutSize;

    private final DataSource dataSource;

    private final int fetchSize;

    private final String localStorePath;

    private final ExpTaskExecuteLogModel expTaskExecuteLogModel;

    private final ExpTaskModel expTaskModel;

    private final ExpTaskExecuteDetailLogDaoRepo expTaskExecuteDetailLogDaoRepo;

    private final DataStoreInfoModel dataStoreInfoModel;

    private final ExpRuleDetailModel expRuleDetailModel;

    private String ruleTbl;

    private Date dataDate;

    private final Scheduler scheduler;

    private final ExpTaskDao expTaskDao;

    private final ExpRuleDetailService expRuleDetailService;

    public SplitKeyTaskHandler(DataSource dataSource,
                               ExpRuleDetailModel expRuleDetailModel,
                               DataStoreInfoModel dataStoreInfoModel,
                               ExpTaskExecuteDetailLogDaoRepo expTaskExecuteDetailLogDaoRepo, ExpTaskModel expTaskModel,
                               ExpTaskExecuteLogModel expTaskExecuteLogModel,
                               String localStorePath, int fetchSize, int printOutSize, Scheduler scheduler, ExpTaskDao expTaskDao,
                               ExpRuleDetailService expRuleDetailService) {
        this.dataSource = dataSource;
        this.expRuleDetailModel = expRuleDetailModel;
        this.dataStoreInfoModel = dataStoreInfoModel;
        this.expTaskExecuteDetailLogDaoRepo = expTaskExecuteDetailLogDaoRepo;
        this.expTaskModel = expTaskModel;
        this.expTaskExecuteLogModel = expTaskExecuteLogModel;
        this.localStorePath = localStorePath;
        this.ruleTbl = expRuleDetailModel.getRuleTbl();
        this.fetchSize = fetchSize;
        this.printOutSize = printOutSize;
        this.scheduler=scheduler;
        this.expTaskDao=expTaskDao;
        this.expRuleDetailService = expRuleDetailService;
    }

    /**
     * 初始化分片所需元数据
     * @return
     * @throws Exception
     */
    public Map<String, Object> initSplitKeyTask() throws Exception {

        this.call();

        //获取总数
        int count = expRuleDetailService.getTotalCount(expRuleDetailModel, ruleTbl, dataSource, dataDate).intValue();
        //获取分片数
        int pieces = getPieces(count);
        //获取分片键最大值、最小值、分片键类型
        Map<String, Object> splitKey = expRuleDetailService.getSplitKey(expRuleDetailModel, ruleTbl, dataDate, dataSource);

        Map<String, Object> res = new HashMap<>(splitKey);
        res.put(SplitKeyExecuteConstant.PIECES, pieces);
        res.put(SplitKeyExecuteConstant.TOTAL_COUNT, count);
        return res;
    }

    private void call() {
        String selectSql = null;
        try {
            //生成查询语句
            this.dataDate = RandomNumsUtil.getDataTime(this.expRuleDetailModel.getDataUnit(), this.expRuleDetailModel.getDataPeriod());
        } catch (Exception e) {
            this.expTaskModel.setStatus(3);
            this.expTaskModel.setMessage(e.getMessage().length()>500?e.getMessage().substring(0,500):e.getMessage());
            expTaskDao.updateExpTask(expTaskModel);
            log.error("call error sql:[" + selectSql + "]", e);
        }
    }

    /**
     * 获取单表最大的记录总行数
     */
    private int getPieces(int count) {
        int initrow=expRuleDetailModel.getInitRow() * 10000;
        int threadSingleTableMaxPoolSize;

         //根据表记录总数和初始化分页总数进行分页
        if(count <= initrow){
            threadSingleTableMaxPoolSize = 1;
        }else{
            threadSingleTableMaxPoolSize = count / initrow + (count % initrow == 0 ? 0 : 1);
        }
        log.info("table:{}, maxCount:{}, initrow:{},threadSingleTableMaxPoolSize:{}", this.ruleTbl,count,initrow,threadSingleTableMaxPoolSize);
        return threadSingleTableMaxPoolSize;
    }

    /**
     * 执行粗略分片
     * @param startSplitKeyVal
     * @param endSplitKeyVal
     * @param datafileSuffix
     * @param isFinish
     */
    @SneakyThrows
    public void executeSplitKey(Object startSplitKeyVal, Object endSplitKeyVal, String datafileSuffix, boolean isFinish) {
        StringBuilder selectSql = this.createSelectSql(expRuleDetailModel.getSplitKey(), startSplitKeyVal, endSplitKeyVal, isFinish);
        ExpTaskExecuteDetailLogModel eredlm = this.saveExecuteLog(selectSql, datafileSuffix);
        this.createScheduleSplitKey(eredlm);
    }

    /**
     * 创建粗略分片sql语句
     * @param splitKeyCloumn
     * @param startSplitKeyVal
     * @param endSplitKeyVal
     * @param isFinish
     * @return
     * @throws SchedulerException
     */
    private StringBuilder createSelectSql(String splitKeyCloumn,Object startSplitKeyVal,Object endSplitKeyVal,boolean isFinish) {
        StringBuilder sb = new StringBuilder();
        //自定义查询语句
        if (this.expRuleDetailModel.getRuleTYpe() == QueryTypeEnums.CUSTOM_QUERY.getCode()) {
            sb.setLength(0);
            sb.append("select * from (")
                    .append(RandomNumsUtil.paramsToStr(this.expRuleDetailModel.getRowRange(), this.dataDate)).append(")")
                    .append(" where ").append(splitKeyCloumn).append(" >= ").append(startSplitKeyVal).append(" and ")
                    .append(splitKeyCloumn).append(!isFinish ? " < " : " <= ").append(endSplitKeyVal);
            //1 动态表
            if (this.expRuleDetailModel.getRuleTblType() == 1) {
                this.ruleTbl = RandomNumsUtil.getTable(this.dataDate,
                        this.expRuleDetailModel.getRuleTblSuffix(),
                        this.ruleTbl,
                        this.expRuleDetailModel.getFunValue());
            }
        //非自定义查询语句
        }else {
            sb.append(" SELECT ").append(this.expRuleDetailModel.getColList());
            //全部数据查询语句
            if (this.expRuleDetailModel.getRuleTblType() == TableTypeEnums.NOT_DYNAMIC_TABLE.getCode()) {
                //0 非动态表
                sb.append(" FROM ").append(this.expRuleDetailModel.getSchemaName()).append(".").append(this.ruleTbl);
            }else {
                //1 动态表
                this.ruleTbl = RandomNumsUtil.getTable(this.dataDate,
                        this.expRuleDetailModel.getRuleTblSuffix(),
                        this.ruleTbl,
                        this.expRuleDetailModel.getFunValue());
                sb.append(" FROM ").append(this.expRuleDetailModel.getSchemaName()).append(".").append(this.ruleTbl);
            }

            //部分数据查询语句的条件
            if (this.expRuleDetailModel.getRuleTYpe() == QueryTypeEnums.PART_QUERY.getCode()) {
                sb.append(" WHERE ")
                        .append( RandomNumsUtil.paramsToStr(this.expRuleDetailModel.getRowRange(), this.dataDate))
                        .append("  and ").append(splitKeyCloumn).append(" >= ").append(startSplitKeyVal).append(" and ")
                        .append(splitKeyCloumn).append(!isFinish ? " < " : " <= ").append(endSplitKeyVal);
            }else{
                sb.append(" WHERE ").append(splitKeyCloumn).append(" >= ").append(startSplitKeyVal).append(" and ")
                        .append(splitKeyCloumn).append(!isFinish ? " < " : " <= ").append(endSplitKeyVal);
            }
        }

        log.info("createSelectSql : {}",sb);

        return sb;
    }

    /**
     * 添加分片执行日志
     * @param sb
     * @param datafileSuffix
     * @return
     */
    private ExpTaskExecuteDetailLogModel saveExecuteLog(StringBuilder sb, String datafileSuffix) {
        ExpTaskExecuteDetailLogModel eredlm = new ExpTaskExecuteDetailLogModel();
        String task_execute_detail_id = "t_detail_" + this.ruleTbl + "_" + System.nanoTime()
                + RandomNumsUtil.generateNumber();
        eredlm.setDataFileCount(0);
        eredlm.setTaskId(this.expTaskModel.getTaskId());
        eredlm.setDataFilePath("");
        eredlm.setDataSourceId(this.expTaskModel.getDataSourceId());
        eredlm.setExportStartTime(new Date());
        eredlm.setRuleId(this.expTaskModel.getRuleId());
        eredlm.setRuleTbl(this.ruleTbl);
        eredlm.setRuleType(this.expRuleDetailModel.getRuleTYpe());
        eredlm.setExpStatus(1);
        eredlm.setStoreId(this.expTaskModel.getStoreId());
        eredlm.setStoreType(this.dataStoreInfoModel.getStoreType());
        eredlm.setTaskExecutelogLogId(this.expTaskExecuteLogModel.getTaskExecuteLogId());
        eredlm.setUpdateTime(new Date());
        eredlm.setTaskExecuteDetailId(task_execute_detail_id);
        eredlm.setSqlStr(sb.toString());
        eredlm.setRuleDetailId(this.expRuleDetailModel.getRuleDetailId());
        eredlm.setDataFileSuffix(datafileSuffix);
        this.expTaskExecuteDetailLogDaoRepo.save(eredlm);
        return eredlm;
    }

    /**
     * 创建定时器进行粗略分片
     * @param eredlm
     * @throws SchedulerException
     */
    private void createScheduleSplitKey(ExpTaskExecuteDetailLogModel eredlm) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(JobDetailSingleShardHandler.class)
                .withIdentity(eredlm.getTaskExecuteDetailId(),"simple").build();
        jobDetail.getJobDataMap().put("id",eredlm.getTaskExecuteDetailId());
        jobDetail.getJobDataMap().put("printOutSize",printOutSize);
        jobDetail.getJobDataMap().put("localStorePath",localStorePath);
        jobDetail.getJobDataMap().put("fetchSize",fetchSize);
        //通过指定开始时间和cron表达式,创建触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(eredlm.getTaskExecuteDetailId())
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()).build();

        //关联触发器和任务
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 根据分片健范围获取记录总数
     * @param rightMax
     * @param leftMin
     * @return
     */
    @SneakyThrows
    public BigInteger getCount(BigInteger leftMin, BigInteger rightMax) {
        return expRuleDetailService.getCount(leftMin, rightMax, expRuleDetailModel, ruleTbl, dataSource);
    }
}